/*
 * Copyright (c) 2011-2015 CyanogenMod Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.lineageos.platform.internal;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.database.ContentObserver;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.os.Message;
import android.util.ArraySet;
import com.android.internal.policy.IKeyguardService;
import lineageos.providers.LineageSettings;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.app.ActivityManagerNative;
import android.app.NotificationGroup;
import android.app.backup.BackupManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.os.ParcelUuid;

import lineageos.app.LineageContextConstants;
import lineageos.app.Profile;
import lineageos.app.ProfileGroup;
import lineageos.app.ProfileManager;
import lineageos.app.IProfileManager;

import java.util.Collection;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** @hide */
public class ProfileManagerService extends LineageSystemService {

    private static final String TAG = "LineageProfileService";
    // Enable the below for detailed logging of this class
    private static final boolean LOCAL_LOGV = false;

    public static final String KEYGUARD_PACKAGE = "com.android.systemui";
    public static final String KEYGUARD_CLASS = "com.android.systemui.keyguard.KeyguardService";

    /* package */ static final File PROFILE_FILE =
            new File(Environment.getDataSystemDirectory(), "profiles.xml");

    private static final int MSG_SEND_PROFILE_STATE = 10;

    private Map<UUID, Profile> mProfiles;

    // Match UUIDs and names, used for reverse compatibility
    private Map<String, UUID> mProfileNames;

    private Map<UUID, NotificationGroup> mGroups;

    private Profile mActiveProfile;

    // Well-known UUID of the wildcard group
    private static final UUID mWildcardUUID =
            UUID.fromString("a126d48a-aaef-47c4-baed-7f0e44aeffe5");
    private NotificationGroup mWildcardGroup;

    private Context mContext;
    private Handler mHandler;
    private boolean mDirty;
    private BackupManager mBackupManager;
    private ProfileTriggerHelper mTriggerHelper;
    private Profile mEmptyProfile;

    private Runnable mBindKeyguard = new Runnable() {
        @Override
        public void run() {
            bindKeyguard();
        }
    };
    private IKeyguardService mKeyguardService;
    private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (LOCAL_LOGV) Log.v(TAG, "*** Keyguard connected (yay!)");
            mKeyguardService = IKeyguardService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            if (LOCAL_LOGV) Log.v(TAG, "*** Keyguard disconnected, retrying connection soon.");
            mKeyguardService = null;
            // system UI died? retry connection in 5s
            mHandler.removeCallbacks(mBindKeyguard);
            mHandler.postDelayed(mBindKeyguard, 5000);
        }
    };

    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
                persistIfDirty();
                initialize();
            } else if (action.equals(Intent.ACTION_SHUTDOWN)) {
                persistIfDirty();
            }
        }
    };

    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SEND_PROFILE_STATE:
                    Intent newState = new Intent(ProfileManager.PROFILES_STATE_CHANGED_ACTION);
                    newState.putExtra(ProfileManager.EXTRA_PROFILES_STATE, msg.arg1);

                    mContext.sendBroadcastAsUser(newState, UserHandle.ALL);

                    if (ProfileManager.PROFILES_STATE_ENABLED == msg.arg1) {
                        maybeApplyActiveProfile();
                    }
                    return true;
            }
            return false;
        }
    };

    private void maybeApplyActiveProfile() {
        final List<Profile.ProfileTrigger> wiFiTriggers
                = mActiveProfile.getTriggersFromType(Profile.TriggerType.WIFI);
        final List<Profile.ProfileTrigger> blueToothTriggers
                = mActiveProfile.getTriggersFromType(Profile.TriggerType.BLUETOOTH);

        boolean selectProfile = false;
        if (wiFiTriggers.size() == 0 && blueToothTriggers.size() == 0) {
            selectProfile = true;
        } else {
            final String activeSSID = getActiveSSID();
            if (activeSSID != null) {
                for (Profile.ProfileTrigger trigger : wiFiTriggers) {
                    if (trigger.getState() == Profile.TriggerState.ON_CONNECT
                            && trigger.getId().equals(activeSSID)) {
                        selectProfile = true;
                        break;
                    }
                }
            }
            if (!selectProfile && blueToothTriggers.size() > 0) {
                final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                final Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
                final Set<String> connectedBTDevices = new ArraySet<>();
                for (BluetoothDevice device : pairedDevices) {
                    if (device.isConnected()) connectedBTDevices.add(device.getAddress());
                }
                for (Profile.ProfileTrigger trigger : blueToothTriggers) {
                    if (connectedBTDevices.contains(trigger.getId())
                            && trigger.getState() == Profile.TriggerState.ON_CONNECT) {
                        selectProfile = true;
                        break;
                    }
                }
            }
        }

        if (selectProfile) mActiveProfile.doSelect(mContext, mKeyguardService);
    }

    private String getActiveSSID() {
        final WifiManager wifiManager
                = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
        final WifiInfo wifiinfo = wifiManager.getConnectionInfo();
        if (wifiinfo == null) {
            return null;
        }
        final WifiSsid ssid = wifiinfo.getWifiSsid();
        if (ssid == null) {
            return null;
        }
        return ssid.toString();
    }

    private class ProfilesObserver extends ContentObserver {
        public ProfilesObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            int state = LineageSettings.System.getInt(mContext.getContentResolver(),
                    LineageSettings.System.SYSTEM_PROFILES_ENABLED,
                    ProfileManager.PROFILES_STATE_ENABLED);
            mHandler.obtainMessage(MSG_SEND_PROFILE_STATE, state, 0 /* unused */).sendToTarget();
        }
    }

    public ProfileManagerService(Context context) {
        super(context);
        mContext = context;
        mHandler = new Handler(mHandlerCallback);
        if (context.getPackageManager().hasSystemFeature(
                LineageContextConstants.Features.PROFILES)) {
            publishBinderService(LineageContextConstants.LINEAGE_PROFILE_SERVICE, mService);
        } else {
            Log.wtf(TAG, "Lineage profile service started by system server but feature xml not" +
                    " declared. Not publishing binder service!");
        }
    }

    @Override
    public String getFeatureDeclaration() {
        return LineageContextConstants.Features.PROFILES;
    }

    @Override
    public boolean isCoreService() {
        return false;
    }

    @Override
    public void onStart() {
        mBackupManager = new BackupManager(mContext);

        mWildcardGroup = new NotificationGroup(
                mContext.getString(org.lineageos.platform.internal.R.string.wildcardProfile),
                org.lineageos.platform.internal.R.string.wildcardProfile,
                mWildcardUUID);

        initialize();

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(Intent.ACTION_SHUTDOWN);
        mContext.registerReceiver(mIntentReceiver, filter);
    }

    private void bindKeyguard() {
        if (mKeyguardService == null) {
            Intent intent = new Intent();
            intent.setClassName(KEYGUARD_PACKAGE, KEYGUARD_CLASS);
            if (!mContext.bindServiceAsUser(intent, mKeyguardConnection,
                    Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
                Log.e(TAG, "error binding to keyguard service");
            }
        }
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == PHASE_ACTIVITY_MANAGER_READY) {
            bindKeyguard();
        } else if (phase == PHASE_BOOT_COMPLETED) {
            mContext.getContentResolver().registerContentObserver(
                    LineageSettings.System.getUriFor(LineageSettings.System.SYSTEM_PROFILES_ENABLED),
                    false, new ProfilesObserver(mHandler), UserHandle.USER_ALL);
        }
    }

    private void initialize() {
        initialize(false);
    }

    private void initialize(boolean skipFile) {
        mTriggerHelper = new ProfileTriggerHelper(mContext, mHandler, this);
        mProfiles = new HashMap<UUID, Profile>();
        mProfileNames = new HashMap<String, UUID>();
        mGroups = new HashMap<UUID, NotificationGroup>();
        mEmptyProfile = new Profile("EmptyProfile");
        mDirty = false;

        boolean init = skipFile;

        if (!skipFile) {
            try {
                loadFromFile();
            } catch (XmlPullParserException e) {
                init = true;
            } catch (IOException e) {
                init = true;
            }
        }

        if (init) {
            try {
                initialiseStructure();
            } catch (Throwable ex) {
                Log.e(TAG, "Error loading xml from resource: ", ex);
            }
        }
    }

    private final IBinder mService = new IProfileManager.Stub() {

        @Override
        public void resetAll() {
            long token = clearCallingIdentity();
            enforceChangePermissions();
            initialize(true);
            restoreCallingIdentity(token);
        }

        @Override
        @Deprecated
        public boolean setActiveProfileByName(String profileName) {
            if (!isEnabled()) {
                Log.w(TAG, "Unable to set active profile because profiles are disabled.");
                return false;
            }
            if (!mProfileNames.containsKey(profileName)) {
                // Since profileName could not be casted into a UUID, we can call it a string.
                Log.w(TAG, "Unable to find profile to set active, based on string: " + profileName);
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "setActiveProfile(String) found profile name in mProfileNames.");
            }
            /*
             * We need to clear the caller's identity in order to
             * - allow the profile switch to execute actions
             *   not included in the caller's permissions
             * - broadcast INTENT_ACTION_PROFILE_SELECTED
             */
            long token = clearCallingIdentity();
            setActiveProfileInternal(mProfiles.get(mProfileNames.get(profileName)), true);
            restoreCallingIdentity(token);
            return true;
        }

        @Override
        public boolean setActiveProfile(ParcelUuid profileParcelUuid) {
            if (!isEnabled()) {
                Log.w(TAG, "Unable to set active profile because profiles are disabled.");
                return false;
            }
            /*
             * We need to clear the caller's identity in order to
             * - allow the profile switch to execute actions
             *   not included in the caller's permissions
             * - broadcast INTENT_ACTION_PROFILE_SELECTED
             */
            long token = clearCallingIdentity();
            boolean ret = setActiveProfileInternal(profileParcelUuid.getUuid(), true);
            restoreCallingIdentity(token);
            return ret;
        }

        @Override
        public boolean addProfile(Profile profile) {
            enforceChangePermissions();
            addProfileInternal(profile);
            long token = clearCallingIdentity();
            persistIfDirty();
            restoreCallingIdentity(token);
            return true;
        }

        @Override
        @Deprecated
        public Profile getProfileByName(String profileName) {
            if (mProfileNames.containsKey(profileName)) {
                return mProfiles.get(mProfileNames.get(profileName));
            } else if (mProfiles.containsKey(UUID.fromString((profileName)))) {
                return mProfiles.get(UUID.fromString(profileName));
            } else {
                return null;
            }
        }

        @Override
        public Profile getProfile(ParcelUuid profileParcelUuid) {
            UUID profileUuid = profileParcelUuid.getUuid();
            return getProfileInternal(profileUuid);
        }

        @Override
        public Profile[] getProfiles() {
            Profile[] profiles = getProfileList().toArray(new Profile[0]);
            Arrays.sort(profiles);
            return profiles;
        }

        @Override
        public Profile getActiveProfile() {
            if (!isEnabled()) {
                // Profiles are not enabled, return empty profile
                return mEmptyProfile;
            }
            return getActiveProfileInternal();
        }

        @Override
        public boolean removeProfile(Profile profile) {
            enforceChangePermissions();
            if (mProfileNames.remove(profile.getName()) != null
                    && mProfiles.remove(profile.getUuid()) != null) {
                mDirty = true;
                long token = clearCallingIdentity();
                persistIfDirty();
                restoreCallingIdentity(token);
                return true;
            } else {
                return false;
            }
        }

        @Override
        public void updateProfile(Profile profile) {
            enforceChangePermissions();
            Profile old = mProfiles.get(profile.getUuid());

            if (old == null) {
                return;
            }

            mProfileNames.remove(old.getName());
            mProfileNames.put(profile.getName(), profile.getUuid());
            mProfiles.put(profile.getUuid(), profile);
            /* no need to set mDirty, if the profile was actually changed,
             * it's marked as dirty by itself */
            long token = clearCallingIdentity();
            persistIfDirty();

            // Also update if we changed the active profile
            if (mActiveProfile != null && mActiveProfile.getUuid().equals(profile.getUuid())) {
                setActiveProfileInternal(profile, true);
            }
            restoreCallingIdentity(token);
        }

        @Override
        public boolean profileExists(ParcelUuid profileUuid) {
            return mProfiles.containsKey(profileUuid.getUuid());
        }

        @Override
        @Deprecated
        public boolean profileExistsByName(String profileName) {
            for (Map.Entry<String, UUID> entry : mProfileNames.entrySet()) {
                if (entry.getKey().equalsIgnoreCase(profileName)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        @Deprecated
        public boolean notificationGroupExistsByName(String notificationGroupName) {
            for (NotificationGroup group : mGroups.values()) {
                if (group.getName().equalsIgnoreCase(notificationGroupName)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public NotificationGroup[] getNotificationGroups() {
            return mGroups.values().toArray(new NotificationGroup[0]);
        }

        @Override
        public void addNotificationGroup(NotificationGroup group) {
            enforceChangePermissions();
            addNotificationGroupInternal(group);
            long token = clearCallingIdentity();
            persistIfDirty();
            restoreCallingIdentity(token);
        }

        @Override
        public void removeNotificationGroup(NotificationGroup group) {
            enforceChangePermissions();
            mDirty |= mGroups.remove(group.getUuid()) != null;
            // Remove the corresponding ProfileGroup from all the profiles too if
            // they use it.
            for (Profile profile : mProfiles.values()) {
                profile.removeProfileGroup(group.getUuid());
            }
            long token = clearCallingIdentity();
            persistIfDirty();
            restoreCallingIdentity(token);
        }

        @Override
        public void updateNotificationGroup(NotificationGroup group) {
            enforceChangePermissions();
            NotificationGroup old = mGroups.get(group.getUuid());
            if (old == null) {
                return;
            }

            mGroups.put(group.getUuid(), group);
            /* no need to set mDirty, if the group was actually changed,
             * it's marked as dirty by itself */
            long token = clearCallingIdentity();
            persistIfDirty();
            restoreCallingIdentity(token);
        }

        @Override
        public NotificationGroup getNotificationGroupForPackage(String pkg) {
            for (NotificationGroup group : mGroups.values()) {
                if (group.hasPackage(pkg)) {
                    return group;
                }
            }
            return null;
        }

        @Override
        public NotificationGroup getNotificationGroup(ParcelUuid uuid) {
            if (uuid.getUuid().equals(mWildcardGroup.getUuid())) {
                return mWildcardGroup;
            }
            return mGroups.get(uuid.getUuid());
        }

        @Override
        public boolean isEnabled() {
            long token = clearCallingIdentity();
            try {
                return LineageSettings.System.getIntForUser(mContext.getContentResolver(),
                        LineageSettings.System.SYSTEM_PROFILES_ENABLED,
                        ProfileManager.PROFILES_STATE_ENABLED,
                        UserHandle.USER_CURRENT) == ProfileManager.PROFILES_STATE_ENABLED;
            } finally {
                restoreCallingIdentity(token);
            }
        }
    };

    private void addProfileInternal(Profile profile) {
        // Make sure this profile has all of the correct groups.
        for (NotificationGroup group : mGroups.values()) {
            ensureGroupInProfile(profile, group, false);
        }
        ensureGroupInProfile(profile, mWildcardGroup, true);
        mProfiles.put(profile.getUuid(), profile);
        mProfileNames.put(profile.getName(), profile.getUuid());
        mDirty = true;
    }

    private void ensureGroupInProfile(Profile profile,
                                      NotificationGroup group, boolean defaultGroup) {
        if (profile.getProfileGroup(group.getUuid()) != null) {
            return;
        }

        /* enforce a matchup between profile and notification group, which not only
         * works by UUID, but also by name for backwards compatibility */
        for (ProfileGroup pg : profile.getProfileGroups()) {
            if (pg.matches(group, defaultGroup)) {
                return;
            }
        }

        /* didn't find any, create new group */
        profile.addProfileGroup(new ProfileGroup(group.getUuid(), defaultGroup));
    }

    private Profile getProfileInternal(UUID profileUuid) {
        // use primary UUID first
        if (mProfiles.containsKey(profileUuid)) {
            return mProfiles.get(profileUuid);
        }
        // if no match was found: try secondary UUID
        for (Profile p : mProfiles.values()) {
            for (UUID uuid : p.getSecondaryUuids()) {
                if (profileUuid.equals(uuid)) {
                    return p;
                }
            }
        }
        // nothing found
        return null;
    }

    /* package */ Collection<Profile> getProfileList() {
        return mProfiles.values();
    }

    private String getXmlString() {
        StringBuilder builder = new StringBuilder();
        builder.append("<profiles>\n<active>");
        builder.append(TextUtils.htmlEncode(mActiveProfile.getUuid().toString()));
        builder.append("</active>\n");

        for (Profile p : mProfiles.values()) {
            p.getXmlString(builder, mContext);
        }
        for (NotificationGroup g : mGroups.values()) {
            g.getXmlString(builder, mContext);
        }
        builder.append("</profiles>\n");
        return builder.toString();
    }

    private synchronized void persistIfDirty() {
        boolean dirty = mDirty;
        if (!dirty) {
            for (Profile profile : mProfiles.values()) {
                if (profile.isDirty()) {
                    dirty = true;
                    break;
                }
            }
        }
        if (!dirty) {
            for (NotificationGroup group : mGroups.values()) {
                if (group.isDirty()) {
                    dirty = true;
                    break;
                }
            }
        }
        if (dirty) {
            try {
                Log.d(TAG, "Saving profile data...");
                FileWriter fw = new FileWriter(PROFILE_FILE);
                fw.write(getXmlString());
                fw.close();
                Log.d(TAG, "Save completed.");
                mDirty = false;
                mBackupManager.dataChanged();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    private void enforceChangePermissions() {
        mContext.enforceCallingOrSelfPermission(
                lineageos.platform.Manifest.permission.MODIFY_PROFILES,
                "You do not have permissions to change the Profile Manager.");
    }

    // Called by SystemBackupAgent after files are restored to disk.
    void settingsRestored() {
        initialize();
        for (Profile p : mProfiles.values()) {
            p.validateRingtones(mContext);
        }
        persistIfDirty();
    }

    private void loadFromFile() throws XmlPullParserException, IOException {
        XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();
        XmlPullParser xpp = xppf.newPullParser();
        FileReader fr = new FileReader(PROFILE_FILE);
        xpp.setInput(fr);
        loadXml(xpp, mContext);
        fr.close();
        persistIfDirty();
    }

    private void loadXml(XmlPullParser xpp, Context context) throws
            XmlPullParserException, IOException {
        int event = xpp.next();
        String active = null;
        while (event != XmlPullParser.END_TAG || !"profiles".equals(xpp.getName())) {
            if (event == XmlPullParser.START_TAG) {
                String name = xpp.getName();
                if (name.equals("active")) {
                    active = xpp.nextText();
                    Log.d(TAG, "Found active: " + active);
                } else if (name.equals("profile")) {
                    Profile prof = Profile.fromXml(xpp, context);
                    addProfileInternal(prof);
                    // Failsafe if no active found
                    if (active == null) {
                        active = prof.getUuid().toString();
                    }
                } else if (name.equals("notificationGroup")) {
                    NotificationGroup ng = NotificationGroup.fromXml(xpp, context);
                    addNotificationGroupInternal(ng);
                }
            } else if (event == XmlPullParser.END_DOCUMENT) {
                throw new IOException("Premature end of file while reading " + PROFILE_FILE);
            }
            event = xpp.next();
        }
        // Don't do initialisation on startup. The AudioManager doesn't exist yet
        // and besides, the volume settings will have survived the reboot.
        try {
            // Try / catch block to detect if XML file needs to be upgraded.
            setActiveProfileInternal(UUID.fromString(active), false);
        } catch (IllegalArgumentException e) {
            if (mProfileNames.containsKey(active)) {
                setActiveProfileInternal(mProfileNames.get(active), false);
            } else {
                // Final fail-safe: We must have SOME profile active.
                // If we couldn't select one by now, we'll pick the first in the set.
                setActiveProfileInternal(mProfiles.values().iterator().next(), false);
            }
            // This is a hint that we probably just upgraded the XML file. Save changes.
            mDirty = true;
        }
    }

    private void initialiseStructure() throws XmlPullParserException, IOException {
        XmlResourceParser xml = mContext.getResources().getXml(
                org.lineageos.platform.internal.R.xml.profile_default);
        try {
            loadXml(xml, mContext);
            mDirty = true;
            persistIfDirty();
        } finally {
            xml.close();
        }
    }

    private boolean setActiveProfileInternal(UUID profileUuid, boolean doInit) {
        if (!mProfiles.containsKey(profileUuid)) {
            Log.e(TAG, "Cannot set active profile to: "
                    + profileUuid.toString() + " - does not exist.");
            return false;
        }

        if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(UUID, boolean) found UUID in mProfiles.");
        setActiveProfileInternal(mProfiles.get(profileUuid), doInit);
        return true;
    }

    /* package */ Profile getActiveProfileInternal() {
        return mActiveProfile;
    }

    /* package */ void setActiveProfileInternal(Profile newActiveProfile, boolean doInit) {
        /*
         * NOTE: Since this is not a public function, and all public functions
         * take either a string or a UUID, the active profile should always be
         * in the collection.  If writing another setActiveProfile which receives
         * a Profile object, run enforceChangePermissions, add the profile to the
         * list, and THEN add it.
         */

        enforceChangePermissions();

        Log.d(TAG, "Set active profile to: " + newActiveProfile.getUuid().toString()
                + " - " + newActiveProfile.getName());

        Profile lastProfile = mActiveProfile;
        mActiveProfile = newActiveProfile;
        mDirty = true;

        if (doInit) {
            if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(Profile, boolean) - Running init");
            // Call profile's "doSelect"
            mActiveProfile.doSelect(mContext, mKeyguardService);

            // Notify other applications of newly selected profile.
            Intent broadcast = new Intent(ProfileManager.INTENT_ACTION_PROFILE_SELECTED);
            broadcast.putExtra(ProfileManager.EXTRA_PROFILE_NAME,
                    mActiveProfile.getName());
            broadcast.putExtra(ProfileManager.EXTRA_PROFILE_UUID,
                    mActiveProfile.getUuid().toString());
            broadcast.putExtra(ProfileManager.EXTRA_LAST_PROFILE_NAME,
                    lastProfile.getName());
            broadcast.putExtra(ProfileManager.EXTRA_LAST_PROFILE_UUID,
                    lastProfile.getUuid().toString());
            broadcast.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

            mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
            persistIfDirty();
        } else if (lastProfile != mActiveProfile && ActivityManagerNative.isSystemReady()) {
            // Something definitely changed: notify.
            Intent broadcast = new Intent(ProfileManager.INTENT_ACTION_PROFILE_UPDATED);
            broadcast.putExtra(ProfileManager.EXTRA_PROFILE_NAME,
                    mActiveProfile.getName());
            broadcast.putExtra(ProfileManager.EXTRA_PROFILE_UUID,
                    mActiveProfile.getUuid().toString());
            mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
        }
    }

    private void addNotificationGroupInternal(NotificationGroup group) {
        if (mGroups.put(group.getUuid(), group) == null) {
            // If the above is true, then the ProfileGroup shouldn't exist in
            // the profile. Ensure it is added.
            for (Profile profile : mProfiles.values()) {
                ensureGroupInProfile(profile, group, false);
            }
        }
        mDirty = true;
    }
}
